<?php
/**
 * 文档注释解析
 * 
 * @title 文档注释解析类, 通过反射机制及文档注释辅助来打印出控制器的接口文档
 * 
 * @author Jason 2016-01-07
 * @version 2.0.0
 */
class Lib_docomment
{
	
    public static $innerItems = array(
            'author',
            
            'title',
            'entry',
            'actionRouterUriProtocolRule',
            'structUrl',
            'actionParamsUriProtocolRule',
            'demoParam',
            'command',
    );
    
    /**
     * 过滤掉不想显示在接口文档的参数
     * 
     * @param string $docComment
     * @param array $items
     * 
     * @return string
     */
    public static function filterParams($docComment, $items = array())
    {
        $string = "";
        $lines = explode(PHP_EOL, $docComment);
        foreach ($lines as $line) {
            foreach ($items as $item) {
                if(strpos($line, "@{$item} ")) continue 2;
            }
            $string .= $line . PHP_EOL;
        }
        
        return $string;
    }
    
    /**
     * 获取所有的 “@参数”，注意：仅拉第一行数据
     *
     * @param string $docComment
     *
     * @return array<string>
     */
    public static function getParams($docComment)
    {
        $array = array();
        $lines = explode(PHP_EOL, $docComment);
        foreach ($lines as $line) {
            @list($null, $string) = explode("@", $line);
            if(empty($string)) continue;
            if(!empty(trim($null, "\t *"))) continue; // 跳过对 @entry 的引用
            
            @list($key, $null) = explode(" ", $string);
            //$value = trim(str_replace($key, "", $string));//这个替换肯定是不对的
            $value = trim(substr($string, strlen($key)));
            //$array[$key] = $value;
            if(isset($array[$key])){
                //$array[$key] = (array)($array[$key]);
                is_array($array[$key]) || $array[$key] = array($array[$key]);
                array_push($array[$key], $value);
            } else {
                $array[$key] = $value;
            }
        }
        
        if(empty($array['title']) && isset($lines[1])) {
            $array['title'] = trim($lines[1], "\t *");
        }
        
        return $array;
    }
    
    /**
     * 获取指定的 “@参数”，注意：仅拉第一行数据
     *
     * @param string $docComment
     * @param string $param
     *
     * @return string
     */
    public static function getParam($docComment, $param)
    {
        $array = self::getParams($docComment);
        return empty($array[$param]) ? '' : $array[$param];
    }
    
    /**
     * 根据类对象, 方法对象和手动指定的api基地址组织URL
     * 
     * @param object $classObject   ReflectionClass
     * @param object $methodObject  ReflectionMethod
     * @param string $entry
     * 
     * @return string
     */
    public static function structUrl($classObject, $methodObject, $entry = null)
    {
        $cDocComment = self::getClassComment($classObject);
        $mDocComment = self::getMethodComment($methodObject);
        $entry = empty($entry) ? self::getParam($mDocComment, 'entry') : $entry;
        $entry = empty($entry) ? self::getParam($cDocComment, 'entry') : $entry;
        //var_dump($entry);
        
        //获取手动指定的structUrl
        $structUrl = self::getParam($mDocComment, 'structUrl');
        //var_dump($structUrl);
        $structUrl = str_replace('<entry>', $entry, $structUrl);
        //var_dump($structUrl);
        
        //从demoParams和函数定义中获取参数值, 组成structUrl
        if(empty($structUrl)){
            
            $routerRule = self::getParam($mDocComment, 'actionRouterUriProtocolRule');
            $routerRule = empty($routerRule) ? self::getParam($cDocComment, 'actionRouterUriProtocolRule') : $routerRule;
            $routerRule = empty($routerRule) ? 'segment_slash' : $routerRule;
            
            $paramsRule = self::getParam($mDocComment, 'actionParamsUriProtocolRule');
            $paramsRule = empty($paramsRule) ? self::getParam($cDocComment, 'actionParamsUriProtocolRule') : $paramsRule;
            $paramsRule = empty($paramsRule) ? 'segment_slash' : $paramsRule;
            
            $routerParams = self::getRouterParams($classObject, $methodObject);
            //print_r($routerParams);
            
            $demoParams = self::getDemoParams($methodObject, $mDocComment);
            //print_r($demoParams);
            
            $routerUrl = self::getActionRouterUrl($entry, $routerParams, $routerRule);
            $routerQuery = self::buildActionRouterQuery($demoParams, $paramsRule);
            if ($paramsRule == 'segment_slash') {
            	$glue = '/';
            } else {
            	$glue = (strpos($routerUrl, '?') === false) ? '?' : '&amp;';
            }
            $structUrl = $routerUrl . $glue . $routerQuery;
            
            // MARK 对于 structUrl 强加 debug 参数
            $structUrl .= (strpos($structUrl, '?') === false) ? '?debug=1' : '&amp;debug=1';
        }
        
        return $structUrl;
    }
    
    private static function getActionRouterUrl($entry, $params, $rule)
    {
    	$url = $entry;
    	switch ($rule) {
    		case 'segment_slash':
    			$url .= '/' . self::buildActionRouterQuery($params, 'segment_slash');
    			break;
    		case 'query_string':
    		default:
    			$url .= '?' . self::buildActionRouterQuery($params, 'query_string');
    	}
    	
    	return $url;
    }
    private static function buildActionRouterQuery($params, $rule)
    {
    	$query = '';
    	switch ($rule) {
    		case 'segment_slash':
    			$query = implode('/', array_filter($params, 'strlen'));
    			break;
    		case 'query_string':
    		default:
    			$query = http_build_query($params, '', '&amp;');
    	}
    	
    	return $query;
    }
    
    /**
     * 获得路由级参数
     * 
     * @param object $classObject
     * @param object $methodObject
     * 
     * @return array
     */
    private static function getRouterParams($classObject, $methodObject)
    {
        $className = $classObject->getName();
        $methodName = $methodObject->getName();
        #preg_match("/([^\/]*?)\/([^\/]*?)\.php/", $classObject->getFileName(), $matches); // runtime OK but bad escape sequence:\.
        #preg_match("/([^\/]*?)\/([^\/]*?).php/", $classObject->getFileName(), $matches); // Something Bad!
        #preg_match("/([^\/]*?)\/([^\/]*?)\\.php/", $classObject->getFileName(), $matches); // OK
        #preg_match("/([^\\/]*?)\\/([^\\/]*?)\\.php/", $classObject->getFileName(), $matches); // Good!
        #print_r($matches);exit;
        
        $directory = basename(dirname($classObject->getFileName()));
        $directory = ($directory === 'controllers') ? '' : $directory;
        
        //var_dump($directory,$className,$methodName);exit;
        
        $router = array(
                's' => strtolower($directory),
                'c' => strtolower($className),
                'a' => strtolower($methodName),
        );
        return $router;
    }
    
    /**
     * 获得接口的数据参数
     * 
     * @param object $methodObject
     * @param object $mDocComment
     * 
     * @return array
     */
    private static function getDemoParams($methodObject, $mDocComment)
    {
        //将注释中的demoParams整理成关联数组
        $demoParamsArray = self::getParam($mDocComment, 'demoParam');
        //$demoParamsArray = is_array($demoParamsArray) ? $demoParamsArray : array();//bug!
        $demoParamsArray = is_array($demoParamsArray) ? $demoParamsArray : array($demoParamsArray);
        $demoParams = array();
        foreach ($demoParamsArray as $key_value) {
            //@list($key, $value) = explode(" ", $key_value);
            @list($key, $value) = preg_split("/\s+/", $key_value);//不用explode函数是为了更整齐的代码格式,否则只能用一个空格
            $value = trim(substr($key_value, strlen($key)));//留住值中的空白字符
            $demoParams[$key] = $value;
        }
        //print_r($demoParams);
        
        //融入默认值
        $methodParams = array();
        foreach ($methodObject->getParameters() as $param) {
            if (isset($demoParams[$param->getName()])) {
                $methodParams[$param->getName()] = $demoParams[$param->getName()];
            } else if ($param->isOptional()) {
                $methodParams[$param->getName()] = $param->getDefaultValue();
            } else {
                trigger_error("The parameter [{$param->getName()}] of method [{$methodObject->getName()}] has no DefaultValue!");
            }
        }
        //print_r($methodParams);
        $methodParams = array_merge($demoParams, $methodParams);
        
        return $methodParams;
    }
    
    
    
    /**
     * 根据类名获得类的反射对象
     *
     * @param object $className
     *
     * @return object
     */
    public static function getClassObject($className)
    {
        $rClass = new ReflectionClass($className);
        return $rClass;
    }
    
    /**
     * 根据类的反射对象获得方法对象（仅限控制器）
     *
     * @param string $className
     *
     * @return array<object>
     */
    public static function getMethodObjects($classObject)
    {
        $methods = $classObject->getMethods(ReflectionMethod::IS_PUBLIC);
        //print_r($methods);
        
        $actionMethods = array();
        foreach ($methods as $method) {
        	if (substr($method->getName(), 0, 2) === '__') {
        		continue;
        	}
        	if($method->class !== $classObject->getName()){
        		continue;
        	}
        	array_push($actionMethods, $method);
        }
        
        return $actionMethods;
    }
    
    /**
     * 根据类的反射对象获得文档注释
     *
     * @param object $methodObject
     *
     * @return string
     */
    public static function getClassComment($classObject)
    {
        $docComment = $classObject->getDocComment();
        return $docComment;
    }
    
    /**
     * 根据方法的反射对象获得方法的文档注释
     *
     * @param object $methodObject
     *
     * @return string
     */
    public static function getMethodComment($methodObject)
    {
        $docComment = $methodObject->getDocComment();
        return $docComment;
    }
    
    
    
    
    
    /**
     * 根据目录名获取所有类的类名列表（仅限控制器）
     *
     * @param string $dirName
     *
     * @return array<string>
     */
    public static function getClassNames($dirName)
    {
    	$files = self::getFileFromPath($dirName, false);
    	
    	$classNames = array();
    	foreach ($files as $file) {
    		//if(strpos($file, '/doc.control.php')) continue;
    		$className = self::getClassName($file);
    		$className && array_push($classNames, $className);
    	}
    	return $classNames;
    }
    
    /**
     * 根据文件名获得类名（仅限控制器）
     *
     * @param string $file
     *
     * @return string|null
     */
    public static function getClassName($file)
    {
    	if(false !== strpos($file, '.php')) {
    		$fileName = basename($file);
    		$dirName = basename(dirname($file));
    		$ctl = ucfirst(str_replace('.php', '', $fileName));
    		
    		return $ctl;
    	}
    	
    	return null;
    }
    
    
    /**
     * @desc 获得目录下的所有文件路径
     *
     * @param string  $path
     * @param boolean $recursion 是否递归
     *
     * @return array
     */
    private static function getFileFromPath($path, $recursion = FALSE)
    {
    	if (!$handle = @opendir($path)) return false;
    	if (substr($path, -1) != '/') $path .= "/";
    	
    	$retPath    = array();
    	$new        = array();
    	while (false !== ($file = readdir($handle))) {
    		if ($file != "." && $file != "..") {
    			if (is_dir($path.$file)) {
    				if ($recursion == FALSE) continue;
    				$new        = self::getFileFromPath($path.$file, $recursion);
    				$retPath    = array_merge($retPath, $new);
    				$new        = array();
    			} else {
    				$retPath[]    = $path . $file;
    			}
    		}
    	}
    	
    	return $retPath;
    }
    
    /**
     * 控制器注册函数
     *
     * @desc    控制器注册函数：只负责自动加载控制器，用于反射取接口文档注释。
     * @author  Jason
     * @date    2017年6月23日 下午7:31:00
     *
     * @param   string $controllerName	未加载的类名
     * @param   string $applicationPath	本次反射的所有类所在的目录路径（绝对路径或者相对于index.php的相对路径，最好是全路径）
     *
     * @return  boolean 成功或失败
     */
    private static function __autoload_controller($controllerName, $applicationPath)
    {
    	$filePath = $applicationPath . "/" . $controllerName . ".php";
    	
    	if (!file_exists($filePath)) {
    		return false;
    	} else {
    		include_once $filePath;
    	}
    	
    	if (!class_exists($controllerName, false)) {
    		return false;
    	}
    	
    	return true;
    }
    
    /**
     * 自动加载控制器注册函数
     *
     * @desc    自动加载控制器注册函数：只负责自动加载控制器，用于反射取接口文档注释。
     * @author  Jason
     * @date    2017年6月23日 下午7:31:00
     *
     * @//param   string $classPath	本次反射的所有类所在的目录路径（绝对路径或者相对于index.php的相对路径，最好是全路径）
     * @param   string $directory	本次反射的所有类所在的直接上级目录名称
     *
     * @return  void
     */
    public static function spl_autoload_register_controller($directory)
    {
    	$classPath = APPPATH . "controllers" . DIRECTORY_SEPARATOR . $directory;
    	
    	return spl_autoload_register(function($className) use ($classPath) {
    		return self::__autoload_controller($className, $classPath);
    	});
    }
}
